package org.custom.spring.boot.mybatis.service.plugin.algorithm.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.mapping.BoundSql;
import org.custom.spring.boot.constant.MagicValue;
import org.custom.spring.boot.mybatis.annotations.OptimisticLock;
import org.custom.spring.boot.mybatis.constant.MySqlConstant;
import org.custom.spring.boot.mybatis.constant.MybatisConstant;
import org.custom.spring.boot.mybatis.entity.MybatisAlgorithmMetadata;
import org.custom.spring.boot.mybatis.entity.OptimisticLocking;
import org.custom.spring.boot.mybatis.service.plugin.algorithm.MybatisAlgorithm;
import org.custom.spring.boot.mybatis.utils.MybatisPlugInUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import static org.custom.spring.boot.constant.MagicValue.PLACEHOLDER;
import static org.custom.spring.boot.mybatis.constant.MySqlConstant.*;

/**
 * Mybatis乐观锁插件实现逻辑
 *
 * @author 李坤
 */
public class MybatisOptimisticLockAlgorithm implements MybatisAlgorithm {
    private static final Logger logger = LoggerFactory.getLogger(MybatisOptimisticLockAlgorithm.class);

    @Override
    public String execute(MybatisAlgorithmMetadata algorithmMetadata) {
        BoundSql boundSql = algorithmMetadata.getBoundSql();
        MapperMethod.ParamMap<Object> paramMap = MybatisPlugInUtil.parse2ParamMap(boundSql.getParameterObject());

        // 原SQL数据
        String metaSql = boundSql.getSql();
        // 检验是否支持乐观锁并获取该字段信息
        OptimisticLocking optimisticLocking = this.checkThenObtainOptimisticLockVersion(algorithmMetadata.getLabel(), paramMap);

        if (ObjectUtil.isNotEmpty(optimisticLocking)) {
            // 获取当前乐观锁字段的值
            Integer version;

            if (Objects.nonNull(version = this.obtainLockVersionValue(optimisticLocking.getField(), paramMap))) {
                String column = optimisticLocking.getColumn();
                metaSql = this.appendLockVersion2SetStatement(metaSql, column, version);
                metaSql = this.appendLockVersion2WhereStatement(metaSql, column, version);
            }
        }
        return metaSql;
    }

    /*------------------------------------------------private methods-------------------------------------------------*/

    /**
     * 在 Set 语句中添加 version 字段
     *
     * @param metaSql 原SQL数据
     * @param version 乐观锁字段
     * @param value   乐观锁字段值
     *
     * @return 拼接后的SQL
     */
    private String appendLockVersion2SetStatement(String metaSql, String version, int value) {
        // 格式化原SQL数据
        metaSql = MybatisPlugInUtil.formatSql(metaSql);
        // 判断并获取合适的关键字数据-字符串切割点，索引为0的位值存储的数据为 关键字
        String[] splitKeyword = MybatisPlugInUtil.splitSuitableKeywordByMetaSql(metaSql, WHERE);

        // 判断是否存在合适的关键字 - 如果不存在表明 SQL/代码 有一处出现错误 && 判断原SQL中是否已设置乐观锁字段值
        if (ObjectUtil.isNotEmpty(splitKeyword) && !splitKeyword[1].contains(version + MySqlConstant.EQUAL)) {
            // 判断原SQL中是否已设置乐观锁字段值
            StringBuilder builder = splitMetaSql2StringBuilder(splitKeyword);
            // 在原SQL中添加乐观锁字段并设置 其值 + 1
            builder.insert(splitKeyword[1].length(), OPTIMISTIC_LOCK.replace(PLACEHOLDER, version) + (value + 1));

            metaSql = builder.toString();
        }

        return metaSql;
    }

    private String appendLockVersion2WhereStatement(String metaSql, String version, int value) {
        // 格式化原SQL数据
        metaSql = MybatisPlugInUtil.formatSql(metaSql);
        // 判断并获取合适的关键字数据-字符串切割点
        String[] splitKeyword = MybatisPlugInUtil.splitSuitableKeywordByMetaSql(metaSql, WHERE);
        // 判断原SQL中是否已在条件语句中设置乐观锁字段值
        if (ObjectUtil.isNotEmpty(splitKeyword) && !determineWhetherWhereStatementContainsVersion(metaSql, version)) {
            metaSql = metaSql + AND + version + MySqlConstant.EQUAL + value;
        }
        return metaSql;
    }

    /**
     * 判断是否需要进行乐观锁优化
     *
     * @param label 执行方法全类型限定名
     *
     * @return 乐观锁字段
     */
    private OptimisticLocking checkThenObtainOptimisticLockVersion(String label, MapperMethod.ParamMap paramMap) {
        AtomicReference<OptimisticLocking> optimisticLocking = new AtomicReference<>();
        Class[] classes = this.obtainParameterType(paramMap);

        try {
            int index = label.lastIndexOf(MagicValue.POINT);
            Optional.ofNullable(Class.forName(label.substring(0, index)).getMethod(label.substring(index + 1), classes))
                    .flatMap(dataMethod -> Arrays.stream(dataMethod.getAnnotations())
                            .filter(annotation -> annotation.annotationType().isAssignableFrom(OptimisticLock.class)).findFirst())
                    .ifPresent(annotation -> {
                        OptimisticLock optimisticLock = (OptimisticLock) annotation;
                        optimisticLocking.set(new OptimisticLocking(optimisticLock.field(), optimisticLock.column()));
                    });
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return optimisticLocking.get();
    }

    /**
     * 获取给定 ParamMap 中各参数的类型
     *
     * @param paramMap 参数Map集合
     *
     * @return 各参数类型
     */
    private Class[] obtainParameterType(MapperMethod.ParamMap paramMap) {
        Class[] classes = null;
        if (ObjectUtil.isNotEmpty(paramMap)) {
            int size = paramMap.size();
            int length = (int) Math.ceil((double) size / 2);

            classes = new Class[length];
            for (int index = 0; index < length; index++) {
                classes[index] = paramMap.get(MybatisPlugInUtil.getParam(index)).getClass();
            }
        }
        return classes;
    }

    /**
     * 获取给定version字段在参数映射表中的数据
     *
     * @param lockVersion 乐观锁字段
     * @param paramMap    参数映射
     *
     * @return 乐观锁字段值
     */
    private Integer obtainLockVersionValue(String lockVersion, MapperMethod.ParamMap<Object> paramMap) {
        Integer version = null;
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            if (String.valueOf(entry.getKey()).startsWith(MybatisConstant.PARAM)) {
                if (entry.getValue() instanceof Map) {
                    Map map = (Map) entry.getValue();
                    if (map.containsKey(lockVersion)) {
                        version = Integer.valueOf(map.get(lockVersion).toString());
                        break;
                    }
                } else {
                    Object value = ReflectUtil.getFieldValue(entry.getValue(), lockVersion);
                    // 判断给定的对象中是否存在给定的乐观锁字段
                    if (ObjectUtil.isNotEmpty(value)) {
                        version = Integer.valueOf(value.toString());
                        break;
                    }
                }
            }
        }
        return version;
    }

    private StringBuilder splitMetaSql2StringBuilder(String[] splitByPoint) {
        StringBuilder builder = new StringBuilder();
        if (ObjectUtil.isNotEmpty(splitByPoint)) {
            String keyword = splitByPoint[0];
            for (int index = 1; index < splitByPoint.length; index++) {
                builder.append(splitByPoint[index]);
                if (index != splitByPoint.length - 1) {
                    builder.append(keyword);
                }
            }
        }
        return builder;
    }

    /**
     * 判断原SQL中是否已包含乐观锁字段
     *
     * @param metaSql 原SQL
     * @param version 乐观锁字段
     *
     * @return 判断结果
     */
    private boolean determineWhetherWhereStatementContainsVersion(String metaSql, String version) {
        String[] splitByWhere = metaSql.split(WHERE);
        return splitByWhere[1].contains(version + MySqlConstant.EQUAL);
    }
}
